#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <limits.h>
#include <time.h>
#include <glib.h>

#include "dict.h"
#include "date.h"
#include "type.h"

#define ENCRYPTED_DATE_LENGTH 4


/*
 * date_encrypt_with_dict, encrypt date_str with dicts
 * date_str: string of a date
 * dicts: dictionary
 * return encrypted date string, 
 */
char* date_encrypt_with_dict(const char* date_str, struct dict_set* dicts);

/*
 * date_decrypt_with_dict, decrypt encrypted_date string with dicts to struct tm
 */
struct tm* date_decrypt_with_dict(const char* encrypted_date, struct dict_set *dicts);

/*
 * yday_to_mday, convert day of year to day of month
 */
static void yday_to_mday (int year, int yday, int* month, int* mday);

/*
 * date_validation_check, check if a date if valid
 */
static int date_validation_check(int year, int month, int day);

/*
 * date_to_tm, convert date with year, month and mday to struct tm
 */
static struct tm* date_to_tm(int year, int month, int mday)
{
	int _month, _mday = -1;
	struct tm* terminal = NULL;

	if(!date_validation_check(year, month, mday))
	{
		return NULL;
	}

	terminal = (struct tm*)malloc(sizeof(struct tm));

	if(!terminal)
		return NULL;


	if(year < 2000 || year > 2099)
	{
		free(terminal);
		return NULL;
	}

	// since 1900
	terminal->tm_year = year - 1900;
	// [0, 11]
	terminal->tm_mon = month - 1;
	terminal->tm_mday = mday;
	terminal->tm_sec = 0;
	terminal->tm_min = 0;
	terminal->tm_hour = 0;
	terminal->tm_isdst = -1;

	mktime(terminal);
	yday_to_mday(year, terminal->tm_yday, &_month, &_mday);

	if((month - 1 != _month) || (mday != _mday))
	{
		free(terminal);
		return NULL;
	}

	return terminal;
}

/*
 * _date_diff, get the number of days from date new to date old
 */
static int _date_diff(struct tm* new, struct tm* old)
{
	int diff = 0;
	int year = 0;

	diff = new->tm_yday - old->tm_yday;

	for(year = old->tm_year; year < new->tm_year; ++year)
	{
		if((year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)))
			diff += 366;
		else
			diff += 365;
	}

	return diff;
}

/*
 * date_diff, get the number of days from date current to date expire
 */
static int date_diff(struct tm* current, struct tm* expire)
{

	if(date_is_newer(current, expire))
		return _date_diff(current, expire);
	else
		return - _date_diff(expire, current);
}

/*
 * date_diff_expiration, get number of days from now to expire date
 */
int date_diff_expiration(struct tm* expire)
{
	time_t timep;
	struct tm* p = NULL;

	time(&timep);

	p = localtime(&timep);

	if(!p || !expire)
		return INT_MAX;

	return date_diff(p, expire);
}

/* date_is_newer, tell which expire date is newer
 * new_expire: new expire date
 * old_expire: old_expire
 * return 1 if new_expire is newer than old_expire, or 0 if not
 */
int date_is_newer(struct tm* new_expire, struct tm* old_expire)
{
	if(!new_expire)
		return 0;
	if(!old_expire)
		return 1;

	if(new_expire->tm_year < old_expire->tm_year)
		return 0;

	if(new_expire->tm_year == old_expire->tm_year)
	{
		if(new_expire->tm_yday > old_expire->tm_yday)
			return 1;
		else
			return 0;
	}
	return 1;
}

/* date_newer, get the newer expire date
 * return the newer one in expire date a and b
 */
struct tm* date_newer(struct tm* a, struct tm* b)
{
	if(date_is_newer(a, b))
		return a;
	else
		return b;
}

/*
 * date_expired, tell if expire_date has expired
 */
int date_expired(struct tm* expire_date)
{
	time_t timep;
	struct tm* p = NULL;

	time(&timep);

	p = localtime(&timep);

	if(!p || !expire_date)
		return INT_MAX;

	if(p->tm_year < expire_date->tm_year)
		return 0;

	if(p->tm_year == expire_date->tm_year)
	{
		if(p->tm_yday >= expire_date->tm_yday)
			return 1;
		else
			return 0;
	}
	return 1;
}

/*
 * date_format_check, check if date format is valid
 * return 1 if valid, or 0 if not
 */
static int date_format_check(const char* date_str)
{
	int length;
	int ret = 1;
	int i = 0;

	length = strlen(date_str);

	if(length != 8)
		ret = 0;
	else
	{
		for(i = 0; i < length; ++i)
			if(date_str[i] < '0' || date_str[i] > '9')
			{
				ret = 0;
				break;
			}
	}
	if(!ret)
	{
		//fprintf(stderr, "日期格式非法\n");
		//fprintf(stderr, "日期应当由8位数字组成\n");
		//fprintf(stderr, "例如： 20150401\n");
	}

	return ret;
}

/*
 * date_validation_check, check if date with year, month and mday is valid
 * return 1 if valid, or 0 if not
 */
static int date_validation_check(int year, int month, int mday)
{
	int day_wrong = 0;
	int leap;

	if(year < 2000 || year > 2099)
	{
		//fprintf(stderr, "年范围 [2000, 2099]\n");
		return 0;
	}

	if(month < 1 || month > 12)
	{
		//fprintf(stderr, "月范围 [1, 12]\n");
		return 0;
	}

	leap = (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0));

	switch(month)
	{
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
			if(mday < 1 || mday > 31)
				day_wrong = 1;
			break;
		case 4:
		case 6:
		case 9:
		case 11:
			if(mday < 1 || mday > 30)
				day_wrong = 1;
			break;
		case 2:
			if(leap)
			{
				if(mday < 1 || mday > 29)
					day_wrong = 1;
			}
			else
			{	
				if(mday < 1 || mday > 28)
					day_wrong = 1;
			}
			break;
		default:
			break;
	}
	if(day_wrong)
	{
		//fprintf(stderr, "日期数字无效\n");
		return 0;
	}

	return 1;
}

/*
 * date_str_validation_check, check if date of date_str, month and mday is valid
 * return 1 if valid, or 0 if not
 */
static int date_str_validation_check(const char* date_str, int* year, int* month, int* mday)
{
	if(!date_format_check(date_str))
		return 0;

	int _year, _month, _mday;

	_year = (date_str[0] - '0') * 1000 +
		(date_str[1] - '0') * 100 +
		(date_str[2] - '0') * 10 +
		(date_str[3] - '0');

	_month = (date_str[4] - '0') * 10 +
		(date_str[5] - '0');

	_mday = (date_str[6] - '0') * 10 +
		(date_str[7] - '0');


	if(!date_validation_check(_year, _month, _mday))
		return 0;

	if(year)
		*year = _year;
	if(month)
		*month = _month;
	if(mday)
		*mday = _mday;

	return 1;

}

/*
 * yday_to_mday, convert yday to mday
 */
static void yday_to_mday (int year, int yday, int* month, int* mday)
{
	int leap;
	int common_y[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int leap_y[12] =   {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int _month, _mday;
	int _yday = yday + 1;
	int i = 0;
	_month = 0;
	_mday = 0;

	leap = (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0));

	if(leap)
	{
		for(i = 0; i < 12; ++i)
		{
			if(_yday > leap_y[i])
			{
				_yday -= leap_y[i];
				_month++;
			} else {
				_mday = _yday;
				break;
			}
		}
	} else {
		for(i = 0; i < 12; ++i)
		{
			if(_yday > common_y[i])
			{
				_yday -= common_y[i];
				_month++;
			} else {
				_mday = _yday;
				break;
			}
		}
	}

	if(month)
		*month = _month;
	if(mday)
		*mday = _mday;
}

/*char* date_encrypt(const char* date_str)
{
	return date_encrypt_with_dict(date_str, &normal);
}*/

/*
 * date_encrypt_with_dict, encrypt date_str with dicts
 * date_str: string of a date
 * dicts: dictionary
 * return encrypted date string, 
 */

char* date_encrypt_with_dict(const char* date_str, struct dict_set* dicts)
{
	int year, month, mday = -1;
	int t_year = -1;
	char* crypted_date = NULL;
	int yday = -1;	

	struct tm terminal;


	if(!date_str_validation_check(date_str, &t_year, &month, &mday))
		return NULL;

	terminal.tm_sec = 0;
	terminal.tm_min = 0;
	terminal.tm_hour = 0;
	terminal.tm_mday = mday;
	// For month [0,11]
	terminal.tm_mon = month - 1;
	// For year, years passed from 1900 on
	terminal.tm_year = t_year - 1900;
	terminal.tm_yday = -1;
	terminal.tm_isdst = -1;

	mktime(&terminal);

	if(terminal.tm_yday != -1)
		yday = terminal.tm_yday;

	year = terminal.tm_year;

	crypted_date = (char*)malloc(ENCRYPTED_DATE_LENGTH + 1);
	if(!crypted_date)
		return NULL;

	crypted_date[4] = '\0';

	crypted_date[0] = dicts->year_dict[year / sizeof(dicts->year_dict)];
	crypted_date[1] = dicts->year_dict[year % sizeof(dicts->year_dict)];
	crypted_date[2] = dicts->yday_dict[yday / sizeof(dicts->yday_dict)];
	crypted_date[3] = dicts->yday_dict[yday % sizeof(dicts->yday_dict)];

	//printf("%4d %4d\n", year, yday);

	return crypted_date;
}

/*struct tm* date_decrypt(const char* encrypted_date)
{
	return date_decrypt_with_dict(encrypted_date, &normal);
}*/

/*
 * date_decrypt_with_dict, decrypt encrypted_date string with dicts to struct tm
 */
struct tm* date_decrypt_with_dict(const char* encrypted_date, struct dict_set *dicts)
{
	int year, yday, month, mday = -1;
	int t_year = -1;
	struct tm* terminal;

	if(!encrypted_date)
		return NULL;

	terminal = (struct tm*)malloc(sizeof(struct tm));
	if(!terminal)
		return NULL;


	if(!char_in_dict(dicts->year_dict, sizeof(dicts->year_dict), encrypted_date[0]) ||
	!char_in_dict(dicts->year_dict, sizeof(dicts->year_dict), encrypted_date[1]) ||
	!char_in_dict(dicts->yday_dict, sizeof(dicts->yday_dict), encrypted_date[2]) ||
	!char_in_dict(dicts->yday_dict, sizeof(dicts->yday_dict), encrypted_date[3]))
	{
		free(terminal);
		return NULL;
	}

	year = index_in_dict(dicts->year_dict, sizeof(dicts->year_dict),encrypted_date[0]) * sizeof(dicts->year_dict)
		+ index_in_dict(dicts->year_dict, sizeof(dicts->year_dict),encrypted_date[1]);
	yday = index_in_dict(dicts->yday_dict, sizeof(dicts->yday_dict),encrypted_date[2]) * sizeof(dicts->yday_dict)
		+ index_in_dict(dicts->yday_dict, sizeof(dicts->yday_dict),encrypted_date[3]);

	//printf("%4d %4d\n", year, yday);

	t_year = year + 1900;

	if(t_year < 2000 || t_year > 2099 || yday < 0 || yday > 365)
	{
		free(terminal);
		return NULL;
	}

	yday_to_mday(t_year, yday, &month, &mday);

	if(month >= 0 && month < 12)
		terminal->tm_mon = month;
	if(mday >= 1 && mday <= 31)
		terminal->tm_mday = mday;

	terminal->tm_sec = 0;
	terminal->tm_min = 0;
	terminal->tm_hour = 0;
	terminal->tm_year = year;
	terminal->tm_isdst = -1;

	mktime(terminal);

	if(yday != terminal->tm_yday)
	{
		free(terminal);
		return NULL;
	}

	return terminal;
}

/*
 * date_string_to_tm, convert date string to struct tm
 */
struct tm* date_string_to_tm(const char* date_str)
{
	char* p = NULL;
	gchar** list;
	struct tm* terminal;
	int year, month, mday = -1;

	if(!date_str || strlen(date_str) == 0)
		return NULL;

	p = strstr(date_str, "-");

	if((p == NULL) && date_str_validation_check(date_str, &year, &month, &mday))
		terminal = date_to_tm(year, month, mday);
	else
	{
		list = g_strsplit(date_str, "-", -1);
		if(list)
		{
			year = (list[0]?atoi(list[0]):-1);
			month = (list[1]?atoi(list[1]):-1);
			mday = (list[2]?atoi(list[2]):-1);


			if(year == -1 || month == -1 || mday == -1)
			{
				g_strfreev(list);
				return NULL;
			}
			if(!date_validation_check(year, month, mday))
			{
				g_strfreev(list);
				return NULL;
			}
			g_strfreev(list);
			terminal = date_to_tm(year, month, mday);
		}
		else
			return NULL;
	}

	if(!terminal)
		return NULL;

	return terminal;
	
}

/*
 * date_string_expired, tell if the date of date_str has expired
 * return 1 if expired, or 0 if not
 */
int date_string_expired(const char* date_str)
{
	struct tm* terminal;
	int expired = 0;

	terminal = date_string_to_tm(date_str);
	if(!terminal)
		return 1;

	expired = date_expired(terminal);

	free(terminal);

	return expired;
}

/*
 * _same_expire_date, check if the expire date in vikey and in system are the same
 * return 1 if yes, or 0 if not
 */
int _same_expire_date(const char* ukey_expire, const char* expire_date)
{
	int iRet = 0;
	struct tm* ukey_expire_tm = NULL;
	struct tm* expire_date_tm = NULL;

	if(!ukey_expire || !expire_date)
		return 0;

	if(0 == strcmp(ukey_expire, expire_date))
		return 1;

	ukey_expire_tm = date_string_to_tm(ukey_expire);
	expire_date_tm = date_string_to_tm(expire_date);

	iRet = _same_expire_date_tm(ukey_expire_tm, expire_date_tm);
	if (ukey_expire_tm) {
		free(ukey_expire_tm);
		ukey_expire_tm = NULL;
	}
	if (expire_date_tm) {
		free(expire_date_tm);
		expire_date_tm = NULL;
	}
	return iRet;
}


#if 0
int main(int argc, char** argv)
{
	char* date_encrypted = NULL;
	struct tm* terminal;	


	if(date_string_expired(argv[1]))
		printf("expired\n");
	else
		printf("not expired\n");

	return 0;
}
#endif
